package org.gluedom.data;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Comparator;

import com.google.common.base.Function;

public abstract class Identifier implements Immutable, Comparable<Identifier> {
	public static final Identifier ROOT_OWNERID = Identifier.id(URI
			.create("http://gluedom.org/ns/owner#root"));

	protected final String string;

	private volatile int hash = 0;

	private Identifier(String string) {
		this.string = checkNotNull(string);
	}

	public String toString() {
		return string;
	}

	public static String marshall(Identifier identifier) {
		if (identifier instanceof StringIdentifier)
			return "S" + identifier.string;
		if (identifier instanceof URIIdentifier)
			return "U" + identifier.string;
		if (identifier instanceof CompoundStringIdentifier)
			return "C" + identifier.string;
		throw new IllegalArgumentException("unknown identifier type '"
				+ identifier + "'");
	}

	public static Identifier unmarshall(String string) {
		checkArgument(string.length() > 0, "argument string is empty");
		char ch = string.charAt(0);
		switch (ch) {
		case 'S':
			return new StringIdentifier(string.substring(1));
		case 'U':
			return new URIIdentifier(URI.create(string.substring(1)));
		case 'C':
			return new CompoundStringIdentifier(string.substring(1));
		}
		throw new IllegalArgumentException(
				"argument string is not a marshalled identifier");
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (obj instanceof Identifier) {
			Identifier other = (Identifier) obj;
			if (hashCode() != obj.hashCode())
				return false;
			return string.equals(other.string);
		}
		return false;
	}

	public int hashCode() {
		if (hash == 0)
			hash = string.hashCode();
		return hash;
	}

	public static Identifier id(Object identifier) {
		if (identifier instanceof String)
			return new StringIdentifier((String) identifier);
		if (identifier instanceof URI)
			return new URIIdentifier((URI) identifier);
		throw new IllegalArgumentException("'" + identifier
				+ "' is not a supported identifier type");
	}

	@Override
	public int compareTo(Identifier identifier) {
		if (identifier == null)
			return 1;
		return string.compareTo(identifier.string);
	}

	public abstract Identifier resolve(Identifier identifier);

	public static String enquote(Identifier identifier) {
		return enquote(identifier.toString());
	}

	public static String enquote(String string) {
		StringBuilder sb = new StringBuilder(string.length() + 16);
		char ch;
		sb.append('"');
		for (int i = 0; i < string.length(); i++) {
			ch = string.charAt(i);
			if (ch == '\\' || ch == '"')
				sb.append('\\');
			sb.append(ch);
		}
		sb.append('"');
		return sb.toString();
	}

	private static class StringIdentifier extends Identifier {
		private StringIdentifier(String string) {
			super(string);
		}

		@Override
		public Identifier resolve(Identifier identifier) {
			return new CompoundStringIdentifier(concatenateStringIdentifiers(
					this, identifier));
		}
	}

	private static class CompoundStringIdentifier extends Identifier {
		private CompoundStringIdentifier(String string) {
			super(string);
		}

		@Override
		public Identifier resolve(Identifier identifier) {
			return new CompoundStringIdentifier(concatenateStringIdentifiers(
					this, identifier));
		}
	}

	private static class URIIdentifier extends Identifier {
		private volatile SoftReference<URI> uri = null;

		private URIIdentifier(URI uri) {
			super((uri = uri.normalize()).toString());
			this.uri = new SoftReference<URI>(uri);
		}

		private URIIdentifier(String uri) {
			super(uri);
		}

		private URI getURI() {
			URI result = uri.get();
			if (result == null) {
				result = URI.create(string);
				this.uri = new SoftReference<URI>(result);
			}
			return result;
		}

		@Override
		public Identifier resolve(Identifier identifier) {
			if (identifier instanceof URIIdentifier)
				return new URIIdentifier(getURI().resolve(
						((URIIdentifier) identifier).getURI()));
			else {
				String encoded = null;
				try {
					encoded = URLEncoder.encode(identifier.string, "UTF-8");
				} catch (UnsupportedEncodingException e) {
					throw new AssertionError(e);
				}
				StringBuilder sb = new StringBuilder(string.length()
						+ encoded.length() + 8);
				int index, i;
				i = string.lastIndexOf('?');
				index = (i < 0 ? string.length() : i);
				i = string.lastIndexOf('#');
				index = (i < 0 ? index : Math.min(index, i));
				sb.append(string.substring(0, index));
				if (index != 0 && string.charAt(index - 1) != '/')
					sb.append('/');
				sb.append(encoded);
				return new URIIdentifier(sb.toString());
			}
		}
	}

	private static String concatenateStringIdentifiers(Identifier i1,
			Identifier i2) {
		StringBuilder sb = new StringBuilder(i1.string.length()
				+ i2.string.length() + 16);
		sb.append((i1 instanceof CompoundStringIdentifier ? i1.string
				: enquote(i1.string)));
		sb.append(":");
		sb.append((i2 instanceof CompoundStringIdentifier ? i2.string
				: enquote(i2.string)));
		return sb.toString();
	}

	static final Function<Identifiable, Identifier> IDENTIFY_FUNCTION = new Function<Identifiable, Identifier>() {
		public Identifier apply(Identifiable i) {
			return i.getIdentifier();
		}
	};

	static final Comparator<Identifiable> IDENTIFYHASH_COMPARATOR = FunctionalHashedComparator
			.fromFunction(IDENTIFY_FUNCTION);
}
